package main

import (
	"github.com/hyperledger/fabric/core/chaincode/shim"
	sc "github.com/hyperledger/fabric/protos/peer"
	"fmt"
	"encoding/json"
	"strconv"
	"bytes"
	"strings"
	"errors"
)

const (
	TOKEN_SYMBOL           = "symbol:"
	TOKEN_PREFIX           = "$$$:"
	I_Account_Regist       = "registAccount"
	I_Account_Info         = "getAccount"
	I_Token_Add            = "addToken"
	I_Token_Transfer       = "transferToken"
	I_Token_TransferTarget = "transferTokenValues"
)

var logger = shim.NewLogger("CCLogger")

type Token struct {
	Owner       string `json:"owner"`
	Total       int    `json:"total"`
	TokenName   string `json:"tokenName"`
	TokenSymbol string `json:"tokenSymbol"`
	Lock        bool   `json:"lock"`
}
type Account struct {
	Username   string            `json:"username"`
	Property   string            `json:"property"`
	TokenValue map[string]string `json:"tokenValue"`
	TokenCount map[string]int    `json:"tokenCount"`
	Lock       bool              `json:"lock"`
}
type SmartContract struct {
}

func (s *SmartContract) initToken(stub shim.ChaincodeStubInterface, args []string) sc.Response {
	var err error
	if len(args) != 4 {
		return shim.Error("Error initToken:Incorrect number of arguments. Expecting 4")
	}
	owner := args[0]
	account, err := s.getAccount(stub, owner)
	if err != nil {
		return shim.Error("get the account error")
	}
	if account.Username == "" {
		logger.Error("get account error: the %s account is not exist", owner)
		return shim.Error("initToken the  account is not exist");
	}
	if account.Lock == true {
		return shim.Error("initToken the account is locked")
	}
	symbol := args[1]
	symbolBytes, err := stub.GetState(TOKEN_SYMBOL + symbol)
	if err != nil {
		return shim.Error("get token symbol error")
	}
	if symbolBytes != nil {
		return shim.Error("this symbol is exist")
	}
	name := args[2]
	total, _ := strconv.Atoi(args[3])
	if (total <= 0) {
		return shim.Error("the total must greater than 0")
	}
	token := &Token{
		Owner:       owner,
		Total:       total,
		TokenName:   name,
		TokenSymbol: symbol,
		Lock:        false}
	tokenAsBytes, _ := json.Marshal(token)
	err = stub.PutState(TOKEN_SYMBOL+symbol, tokenAsBytes)
	if err != nil {
		return shim.Error(err.Error())
	}
	s.initAccountToken(stub, account, symbol, total)
	return shim.Success([]byte("initToken success"))
}
func (s *SmartContract) initAccountToken(stub shim.ChaincodeStubInterface, account Account, symbol string, total int) sc.Response {
	var err error
	var buf bytes.Buffer
	if account.Lock {
		return shim.Error("the account is locked")
	}
	account.TokenCount[symbol] = total;
	for i := 0; i < total; i++ {
		buf.WriteString(strconv.Itoa(i + 1))
		if i != total-1 {
			buf.WriteString(",")
		}
	}
	account.TokenValue[symbol] = buf.String()
	accountBytes, err := json.Marshal(account)
	if err != nil {
		return shim.Error(err.Error())
	}
	err = stub.PutState(account.Username, accountBytes)
	if err != nil {
		return shim.Error(err.Error())
	}

	//err = stub.PutState(TOKEN_PREFIX+account.Username+":"+symbol, tokenValueByte)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success([]byte("addAccountToken success"))
}
func (s *SmartContract) registAccount(stub shim.ChaincodeStubInterface, args []string) sc.Response {
	var err error
	if len(args) != 1 {
		return shim.Error("Error initToken:Incorrect number of arguments. Expecting 1")
	}
	username := args[0]
	userByte, err := stub.GetState(username)
	if err != nil {
		return shim.Error("get account error")
	}
	if userByte != nil {
		return shim.Error("the user has a account")
	}
	tokenValue := make(map[string]string)
	tokenCount := make(map[string]int)
	account := &Account{
		Username:   username,
		Property:   "",
		TokenValue: tokenValue,
		TokenCount: tokenCount,
		Lock:       false}
	accountBytes, err := json.Marshal(account)
	if err != nil {
		return shim.Error(err.Error())
	}
	err = stub.PutState(username, accountBytes)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success([]byte("regist user success"))
}
func (account *Account) setAccountLock(lock bool) {
	account.Lock = lock
}
func (token *Token) setTokenLock(lock bool) {
	token.Lock = lock
}
func (token *Token) getTokenLock() bool {
	return token.Lock
}

func (s *SmartContract) getAccount(stub shim.ChaincodeStubInterface, username string) (Account, error) {
	var err error
	account := &Account{}
	accountBytes, err := stub.GetState(username)
	if err != nil {
		logger.Error("get account error: %s", err.Error())
	}
	if accountBytes != nil {
		json.Unmarshal(accountBytes, &account)
	}
	return *account, err;
}
func (s *SmartContract) queryAccount(stub shim.ChaincodeStubInterface, args [] string) sc.Response {
	var err error
	if len(args) != 1 {
		return shim.Error("Error initToken:Incorrect number of arguments. Expecting 1")
	}
	accountBytes, err := stub.GetState(args[0])
	if err != nil {
		logger.Error("get account error: %s", err.Error())
	}
	return shim.Success(accountBytes)
}
func (s *SmartContract) transferRandom(stub shim.ChaincodeStubInterface, args [] string) sc.Response {
	if len(args) != 4 {
		return shim.Error("Error initToken:Incorrect number of arguments. Expecting 4")
	}
	count, err := strconv.Atoi(args[3])
	if err != nil {
		return shim.Error("Error initToken:Incorrect Type of arguments.")
	}
	return s.transferSymbolRandom(stub, args[0], args[1], args[2], count)
}

func (s *SmartContract) transferSymbolRandom(stub shim.ChaincodeStubInterface, fromUsername string, toUsername string, symbol string, count int) sc.Response {
	var err error
	fromAccount, err := s.getAccount(stub, fromUsername)
	if err != nil {
		return shim.Error(err.Error())
	}
	if !fromAccount.checkBalance(symbol, count) {
		return shim.Error("this account has not enough balance to transfer")
	}
	transferValue := fromAccount.minus(symbol, count)
	toTransferAccount, err := s.getAccount(stub, toUsername)
	toTransferAccount.addus(symbol, transferValue, count)
	s.saveAccount(stub, fromUsername, fromAccount)
	s.saveAccount(stub, toUsername, toTransferAccount)
	return shim.Success([]byte("transfer success"))
}
func (s *SmartContract) saveAccount(stub shim.ChaincodeStubInterface, username string, account Account) error {
	fromBytes, err := json.Marshal(account)
	if err != nil {
		return err
	}
	err = stub.PutState(username, fromBytes)
	if err != nil {
		return err
	}
	return nil
}
func (a *Account) minus(symbol string, count int) string {
	a.TokenCount[symbol] -= count;
	str1, str2 := subString(a.TokenValue[symbol], count)
	a.TokenValue[symbol] = str2
	return str1
}
func (a *Account) addus(symbol string, value string, count int) {
	a.TokenCount[symbol] += count;
	if a.TokenValue[symbol] == "" {
		a.TokenValue[symbol] = value
		return
	}
	a.TokenValue[symbol] += "," + value
}
func subString(str string, count int) (string, string) {
	var buf1 bytes.Buffer
	if count > len(str) {
		return "", str
	}
	s := strings.Split(str, ",")
	for i := 0; i < count; i++ {
		buf1.WriteString(s[i])
		if i != count-1 {
			buf1.WriteString(",")
		}
	}
	str2 := strings.Split(str, buf1.String())
	str2[1] = strings.TrimPrefix(str2[1], ",")
	return buf1.String(), str2[1]
}

func (a *Account) checkBalance(symbol string, count int) bool {
	if _, ok := a.TokenCount[symbol]; ok {
		if _, ok := a.TokenValue[symbol]; ok {
			if a.TokenCount[symbol] >= count {
				return true
			}
		}
	}
	return false;
}
func (a *Account) checkContainsAndTransfer(symbol string, count int, values []string) (string, string, error) {
	if !a.Lock {
		var buf1, buf2 bytes.Buffer
		var tag bool
		oldValueTmp := a.TokenValue[symbol]
		oldValues := strings.Split(oldValueTmp, ",")
		oldLength := len(oldValues)
		newLength := len(values)
		if oldLength > newLength {
			logger.Info("oldLength: %s", strconv.Itoa(oldLength))
			logger.Info("newLength: %s", strconv.Itoa(newLength))
			for i := 0; i < oldLength; i++ {
				tag = false
				logger.Info("i: %s", strconv.Itoa(i))
				for j := 0; j < newLength; j++ {
					logger.Info("j: %s", strconv.Itoa(j))
					if oldValues[i] == values[j] {
						buf2.WriteString(values[j])
						if j != newLength-1 {
							buf2.WriteString(",")
						}
						tag = true
					}
				}
				if !tag {
					buf1.WriteString(oldValues[i])
					if i != oldLength-1 {
						buf1.WriteString(",")
					}
				}
			}
			return buf1.String(), buf2.String(), nil
		}
	}
	return "", "", errors.New("this account is locked or haven't these values")
}
func (s *SmartContract) transferSymbolTargets(stub shim.ChaincodeStubInterface, args [] string) sc.Response {
	count, err := strconv.Atoi(args[3])
	if err != nil {
		return shim.Error("Error initToken:Incorrect Type of arguments.")
	}
	return s.transferSymbolTarget(stub, args[0], args[1], args[2], count, args[4:])
}
func (s *SmartContract) transferSymbolTarget(stub shim.ChaincodeStubInterface, fromUsername string, toUsername string, symbol string, count int, values []string) sc.Response {
	var err error
	if count == 0 || len(values) == 0 || count != len(values) {
		return shim.Error("transfer count or vlaues is incorrect")
	}
	fromAccount, err := s.getAccount(stub, fromUsername)
	if err != nil {
		return shim.Error(err.Error())
	}
	if !fromAccount.checkBalance(symbol, count) {
		return shim.Error("this account has not enough balance to transfer")
	}
	transferValue, err := fromAccount.minusTarget(symbol, count, values)
	if err != nil {
		return shim.Error(err.Error())
	}
	toTransferAccount, err := s.getAccount(stub, toUsername)
	toTransferAccount.addus(symbol, transferValue, count)
	s.saveAccount(stub, fromUsername, fromAccount)
	s.saveAccount(stub, toUsername, toTransferAccount)
	return shim.Success([]byte("transferSymbolTarget success"))
}
func (account *Account) minusTarget(symbol string, count int, values []string) (string, error) {
	var err error
	balance, toValue, err := account.checkContainsAndTransfer(symbol, count, values)
	if err != nil {
		return "", err
	}
	account.TokenCount[symbol] -= count
	account.TokenValue[symbol] = balance
	return toValue, err
}
func (account *Account) initAccount() {

}
func (account *Account) accountStat() bool {
	return account.Lock;
}
func (s *SmartContract) Init(stub shim.ChaincodeStubInterface) sc.Response {
	return shim.Success(nil)
}
func (s *SmartContract) Invoke(stub shim.ChaincodeStubInterface) sc.Response {

	// Retrieve the requested Smart Contract function and arguments
	function, args := stub.GetFunctionAndParameters()
	if function == I_Account_Regist {
		return s.registAccount(stub, args)
	} else if function == I_Account_Info {
		return s.queryAccount(stub, args)
	} else if function == I_Token_Add {
		return s.initToken(stub, args)
	} else if function == I_Token_Transfer {
		return s.transferRandom(stub, args)
	} else if function == I_Token_TransferTarget {
		return s.transferSymbolTargets(stub, args)
	}

	return shim.Error("Invalid Smart Contract function name.")
}
func main() {
	err := shim.Start(new(SmartContract))
	if err != nil {
		fmt.Printf("Error creating new Smart Contract: %s", err)
	}
}
